package cn.gson.querydsl.config;

import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.noear.solon.core.util.ResourceUtil;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

/**
 * <p>****************************************************************************</p>
 * <ul style="margin:15px;">
 * <li>Description : 构建 SessionFactory</li>
 * <li>Version     : 1.0</li>
 * <li>Creation    : 2024年07月19日</li>
 * <li>@author     : ____′↘夏悸</li>
 * </ul>
 * <p>****************************************************************************</p>
 */
@Slf4j
public class HibernateConfiguration extends Configuration {
    private static boolean isInit = false;

    public HibernateConfiguration() {
        if (isInit)
            throw new IllegalArgumentException("HibernateConfiguration已经初始化，不支持多数据源！");
        isInit = true;
    }

    /**
     * 添加实体包扫描，有hibernate的@Table、@Entity
     */
    public void addScanPackage(List<String> basePackage) {
        if (basePackage != null) {
            for (String pack : basePackage) {
                if (!pack.endsWith(".*"))
                    pack += ".*";
                Collection<Class<?>> classes = ResourceUtil.scanClasses(pack);
                for (Class<?> clazz : classes)
                    addAnnotatedClass(clazz);
            }
        }

    }

    /**
     * 添加实体类，有hibernate的@Table、@Entity
     */
    public HibernateConfiguration addAnnotatedClass(Class<?>... entityClass) {
        if (entityClass != null)
            for (Class<?> entity : entityClass)
                addAnnotatedClass(entity);
        return this;
    }

    /**
     * 设置数据源，顶层可以是连接池
     */
    public HibernateConfiguration setDataSource(DataSource dataSource) {
        if (dataSource != null) {
            getProperties().put(AvailableSettings.JAKARTA_JTA_DATASOURCE, dataSource);
        }
        return this;
    }

    /**
     * 设置 hibernate 的 Properties 配置
     * 可以参考 <a href="https://docs.jboss.org/hibernate/orm/6.3/javadocs/org/hibernate/cfg/AvailableSettings.html">
     * https://docs.jboss.org/hibernate/orm/6.3/javadocs/org/hibernate/cfg/AvailableSettings.html
     * </a>
     */
    public HibernateConfiguration setProperties(Properties properties) {
        if (properties != null) {
            properties.forEach((key, value) -> getProperties().put(key, value));
        }
        return this;
    }

    @Override
    public HibernateConfiguration addAnnotatedClass(Class annotatedClass) {
        super.addAnnotatedClass(annotatedClass);
        return addAnnotatedClassName(annotatedClass);
    }

    public HibernateConfiguration addAnnotatedClassName(Class<?> clazz) {
        return addAnnotatedClassName(clazz.getName());
    }

    public HibernateConfiguration addAnnotatedClassName(String className) {
        try {
            Field field = Configuration.class.getDeclaredField("metadataSources");
            field.setAccessible(true);
            MetadataSources metadataSources = (MetadataSources) field.get(this);
            metadataSources.addAnnotatedClassName(className);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return this;
    }

    /**
     * 构建 SessionFactory 工厂，创建 SessionFactory 的成本非常昂贵，因此，对于任何给定的数据库，
     * 应用程序应该只有一个关联的SessionFactory. 维护SessionFactoryHibernate 使用的所有服务，
     * Session(s)例如二级缓存、连接池、事务系统集成等。
     */
    @Override
    public SessionFactory buildSessionFactory() throws HibernateException {
        Object datasource = getProperties().get(AvailableSettings.JAKARTA_JTA_DATASOURCE);
        if (datasource == null)
            throw new IllegalArgumentException("还未配置数据源：AvailableSettings.DATASOURCE: hibernate.connection.datasource");
        else if (!DataSource.class.isAssignableFrom(datasource.getClass())) {
            throw new IllegalArgumentException("hibernate.connection.datasource 不是数据源: " + datasource);
        }

        getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, "thread");
        if (!getProperties().contains(AvailableSettings.CONNECTION_PROVIDER))
            getProperties().put(AvailableSettings.CONNECTION_PROVIDER, new SolonDatasourceConnectionProviderImpl());
        return build();
    }

    private SessionFactory build() {
        log.debug("Building session factory using internal StandardServiceRegistryBuilder");
        getStandardServiceRegistryBuilder().applySettings(getProperties());
        StandardServiceRegistry serviceRegistry = getStandardServiceRegistryBuilder().build();

        try {
            SessionFactory sessionFactory = buildSessionFactory(serviceRegistry);
            Object instance = Proxy.newProxyInstance(
                    this.getClass().getClassLoader(),
                    new Class<?>[]{SessionFactory.class},
                    new SessionFactoryInvocationHandler(sessionFactory)
            );
            return (SessionFactory) instance;
        } catch (Throwable t) {
            serviceRegistry.close();
            throw t;
        }
    }
}
